package org.truenewx.tnxjeex.unit.support;

import java.lang.reflect.Proxy;
import javax.measure.Quantity;
import javax.measure.Unit;

import com.fasterxml.jackson.databind.module.SimpleModule;
import org.apache.commons.lang3.StringUtils;
import org.truenewx.tnxjee.core.enums.annotation.SimpleEnumType;
import org.truenewx.tnxjee.core.util.ClassUtil;
import org.truenewx.tnxjee.core.util.JacksonUtil;
import org.truenewx.tnxjee.core.util.LogUtil;
import org.truenewx.tnxjeex.unit.jackson.MeasureUnitDeserializer;
import org.truenewx.tnxjeex.unit.jackson.MeasureUnitSerializer;
import org.truenewx.tnxjeex.unit.model.MeasureUnit;
import org.truenewx.tnxjeex.unit.util.UnitTypes;
import tech.units.indriya.unit.Units;

public class MeasureUnits extends Units {

    private static final MeasureUnits INSTANCE = new MeasureUnits();

    private MeasureUnits() {
    }

    public static MeasureUnits getInstance() {
        return INSTANCE;
    }

    public static boolean isUnitType(String typeName) {
        return typeName != null && typeName.startsWith(UnitTypes.PREFIX);
    }

    public static <U extends MeasureUnit<?>> void register(Class<U> unitClass) {
        SimpleModule jacksonModule = new SimpleModule();
        jacksonModule.addSerializer(unitClass, new MeasureUnitSerializer<>(unitClass));
        jacksonModule.addDeserializer(unitClass, new MeasureUnitDeserializer<>(unitClass));
        JacksonUtil.DEFAULT_MAPPER.registerModule(jacksonModule);

        ClassUtil.loopStaticPublicField(unitClass, field -> {
            try {
                MeasureUnit<?> unit = (MeasureUnit<?>) field.get(null);
                LogUtil.info(MeasureUnits.class, "Registered measure unit: {}", unit.getName());
            } catch (IllegalAccessException e) {
                LogUtil.error(MeasureUnits.class, e);
            }
        });
    }

    public static String getUnitType(Class<? extends MeasureUnit<?>> unitClass) {
        SimpleEnumType simpleEnumType = unitClass.getAnnotation(SimpleEnumType.class);
        return simpleEnumType == null ? unitClass.getName() : simpleEnumType.name();
    }

    public static String getUnitType(MeasureUnit<?> unit) {
        Class<? extends MeasureUnit<?>> unitClass = getUnitClass(unit);
        return getUnitType(unitClass);
    }

    @SuppressWarnings("unchecked")
    public static <U extends MeasureUnit<Q>, Q extends Quantity<Q>> Class<U> getUnitClass(U unit) {
        Class<?>[] interfaces = unit.getClass().getInterfaces();
        for (Class<?> interfaceType : interfaces) {
            if (MeasureUnit.class.isAssignableFrom(interfaceType)) {
                return (Class<U>) interfaceType;
            }
        }
        // 实际上不会出现这种情况
        throw new RuntimeException();
    }

    @SuppressWarnings("unchecked")
    private static <U extends MeasureUnit<Q>, Q extends Quantity<Q>> U wrap(Unit<Q> originalUnit, Class<U> unitClass,
            String symbol, String name, String value) {
        return (U) Proxy.newProxyInstance(unitClass.getClassLoader(), new Class[]{unitClass},
                new MeasureUnitInvocationHandler<>(originalUnit, symbol, name, value));
    }

    protected <U extends MeasureUnit<Q>, Q extends Quantity<Q>> void addUnit(U unit, String symbol, String name) {
        Helper.addUnit(this.units, unit, name, symbol);
    }

    public <U extends MeasureUnit<Q>, Q extends Quantity<Q>> U add(Unit<Q> originalUnit, Class<U> unitClass,
            String symbol, String name, String value) {
        if (symbol == null) {
            symbol = originalUnit.getSymbol();
            if (symbol == null) {
                symbol = originalUnit.toString();
            }
        }
        U unit = wrap(originalUnit, unitClass, symbol, name, value);
        addUnit(unit, symbol, name);
        return unit;
    }

    public <U extends MeasureUnit<Q>, Q extends Quantity<Q>> U multiply(U baseUnit, double multiplier, String symbol,
            String name, String value) {
        return multiply(baseUnit, multiplier, getUnitClass(baseUnit), symbol, name, value);
    }

    public <U extends MeasureUnit<Q>, Q extends Quantity<Q>> U multiply(Unit<Q> baseUnit, double multiplier,
            Class<U> unitClass, String symbol, String name, String value) {
        U unit = wrap(baseUnit.multiply(multiplier), unitClass, symbol, name, value);
        addUnit(unit, symbol, name);
        return unit;
    }

    public <U extends MeasureUnit<Q>, Q extends Quantity<Q>> U divide(U baseUnit, double divisor, String symbol,
            String name, String value) {
        U unit = wrap(baseUnit.divide(divisor), getUnitClass(baseUnit), symbol, name, value);
        addUnit(unit, symbol, name);
        return unit;
    }

    public MeasureUnit<?> fromKey(String key) {
        if (StringUtils.isNotBlank(key)) {
            for (Unit<?> unit : this.units) {
                if (unit instanceof MeasureUnit) {
                    MeasureUnit<?> measureUnit = (MeasureUnit<?>) unit;
                    if (measureUnit.getKey().equalsIgnoreCase(key)) {
                        return measureUnit;
                    }
                }
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public <U extends MeasureUnit<?>> U fromKey(Class<U> unitClass, String key) {
        if (StringUtils.isNotBlank(key)) {
            for (Unit<?> unit : this.units) {
                if (unitClass.isInstance(unit)) {
                    U u = (U) unit;
                    if (u.getKey().equalsIgnoreCase(key)) {
                        return u;
                    }
                }
            }
        }
        return null;
    }

    public MeasureUnit<?> fromValue(String value) {
        if (StringUtils.isNotBlank(value)) {
            for (Unit<?> unit : this.units) {
                if (unit instanceof MeasureUnit) {
                    MeasureUnit<?> measureUnit = (MeasureUnit<?>) unit;
                    if (measureUnit.getValue().equalsIgnoreCase(value)) {
                        return measureUnit;
                    }
                }
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public <U extends MeasureUnit<?>> U fromValue(Class<U> unitClass, String value) {
        if (StringUtils.isNotBlank(value)) {
            for (Unit<?> unit : this.units) {
                if (unitClass.isInstance(unit)) {
                    U u = (U) unit;
                    if (u.getValue().equalsIgnoreCase(value)) {
                        return u;
                    }
                }
            }
        }
        return null;
    }

    public MeasureUnit<?> fromValue(String type, String value) {
        if (StringUtils.isNotBlank(type) && StringUtils.isNotBlank(value)) {
            for (Unit<?> unit : this.units) {
                if (unit instanceof MeasureUnit) {
                    MeasureUnit<?> measureUnit = (MeasureUnit<?>) unit;
                    if (getUnitType(measureUnit).equals(type) && measureUnit.getValue().equals(value)) {
                        return measureUnit;
                    }
                }
            }
        }
        return null;
    }

    public Class<? extends MeasureUnit<?>> getUnitClass(String type) {
        if (StringUtils.isNotBlank(type)) {
            for (Unit<?> unit : this.units) {
                if (unit instanceof MeasureUnit) {
                    MeasureUnit<?> measureUnit = (MeasureUnit<?>) unit;
                    if (getUnitType(measureUnit).equals(type)) {
                        return getUnitClass(measureUnit);
                    }
                }
            }
        }
        return null;
    }

}
